[Android] surfaceView中VirtualJoystick虚拟摇杆

surfaceView系列01

Posted by Aerber Zhou on 2017-04-18

以下代码均来自hopeful,渣渣我只是解读一下

一.大体

首先在自定义插件VirtualJoystick(后称VJ)中我们需要实现的通过UI进行交互的功能有:

1.跟随手指在屏幕上进行移动

2.在松手时回到中心

3.实现和MemoryTable以及VelocityTable之间数据的交互(主要实现方法还是在MemoryTable以及VelocityTable中)

4.根据传达回来的重力感应数据进行变化

我们先大体看一下这个类中有几个变量和几个方法:

变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   private Thread thread;
//一个线程变量

private SurfaceHolder holder;
//用于管理surface的surfaceholder

private Paint paint;
//画笔变量

private onJoystickMoveListener listener;
//监听VJ是否移动的监听器

private boolean touchDownFlag;
//用于记录是否有按下触屏事件

private boolean istouch;
//是否有触屏事件,用于判断重力感应数据的合法性

private int outer_radius;
//摇杆背景半径

private int inner_radius;
//摇杆半径

private PointF position;
//一个点的坐标变量,包括X坐标和Y坐标两个数据

方法:

二.public VirtualJoystick()

三个构造函数其中主要的内容就是初始化surface的内容,即

init()

给VJ实体化中的变量实体化

三.get函数和set函数

获取当前摇杆坐标的X坐标 Y坐标

获取是否由触屏事件

设置是否由触屏事件

很简单,不细讲

四.private void init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
 private void init(){
//给VJ类中的变量实例化
//实例化paint
paint = new Paint();
//设置paint抗锯齿为true
paint.setAntiAlias(true);
//实例化坐标变量postion并初始化为(0,0)
position = new PointF(0, 0);
//获取当前的holder
//this指的是当前surface
holder = this.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
thread = new Thread(new Runnable() {

public void run() {
while (true) {
try {
mDraw();
Thread.sleep(50);
} catch (InterruptedException e) {

}
}
}
});
thread.start();
}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

public void surfaceDestroyed(SurfaceHolder holder) {
thread.interrupt();
thread = null;
}
});


//添加回调函数surfaceHolder.callback
//并实现callback回调函数
//在callback函数中匿名具体抽象函数surfaceCreated函数、surfaceChanged函数、ssurfaceDestroy函数
//在surfaceCreated函数中实例化thread函数
//并在thread的参数中具体抽象函数runnable
//在runnable中实现run函数
//在run函数中进行绘画,即mdraw()
//再运行thread


// 可以这样写:
// SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
// public void surfaceCreated(SurfaceHolder surfaceHolder) {
// Runnable runnable = new Runnable() {
// public void run() {
// ....
// }
// };
// thread = new Thread(runnable);
// thread.start();
// }
//
// public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
//
// }
//
// public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
// thread.interrupt();
// thread = null;
// }
// };
// holder.addCallback(callback);
}

五、@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

1
2
3
4
5
6
7
8
9
10
11
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//MeasureSpec.getSize():获取父容器的长或者宽
//由于在layout文件中,我们给VJ外套了一个fragment,所以这里计算的是fragment的大小
//Math.min(one,two):取两者中小的那一项,这样保证不超出父容器
//除于2是为了获得背景半径
outer_radius = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)) / 2;
//设置摇杆的半径为背景半径的1/2
inner_radius = outer_radius / 2;
//设置绘制区域是一个以背景半径为边长的正方形
setMeasuredDimension(outer_radius * 2, outer_radius * 2);
}

六、private void mDraw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void mDraw() {
//实例化canvas并初始化为空
Canvas c = null;
try {
//通过 holder.lockCanvas()获取当前画布canvas并锁定画布
c = holder.lockCanvas();
//如果判断canvas为空,无条件返回
if (c == null) return;
//绘制背景为白色
//surfaceview的默认背景是透明,但是android的默认背景是黑色,所以相当于是黑色背景
c.drawColor(Color.WHITE);

//设置画笔paint颜色
paint.setColor(getResources().getColor(R.color.vj_bg));
//绘制背景圆
//参数分别为: 以屏幕左上角为原点的 (圆心x坐标,圆心Y坐标,圆半径,画笔)
c.drawCircle(outer_radius, outer_radius, outer_radius, paint);

paint.setColor(getResources().getColor(R.color.vj_control));
//绘制摇杆
//position,x和position,y是摇杆的偏移量
//向上Y为负,向下Y为正,向左X为负,向右X为正
c.drawCircle(outer_radius + position.x, outer_radius + position.y, inner_radius, paint);
//绘制摇杆圆心
c.drawCircle(outer_radius, outer_radius, inner_radius / 2, paint);
} catch (Exception e) {

} finally {
//在绘制完毕后判断canvas是否为空,如果不为空,则解锁canvas并提交绘制内容
if (c != null)
holder.unlockCanvasAndPost(c);
}
}

七、public boolean onTouchEvent(MotionEvent event)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public boolean onTouchEvent(MotionEvent event) {
//获取触屏的坐标
float tx = event.getX(), ty = event.getY();
//设置触屏事件为true
istouch = true;
switch (event.getAction()) {
//按下 = 0
case MotionEvent.ACTION_DOWN:
//按下操作,设置touchdownflag为true
touchDownFlag = true;

//移动 = 2
case MotionEvent.ACTION_MOVE:
//如果判断并没有按下,则移动失败
if (!touchDownFlag) break;

//获得触屏点在X,Y上于圆心的距离
tx -= outer_radius;
ty -= outer_radius;

//利用勾股定理判断是否触屏点超出背景范围
// 如果超出
if (Math.pow(tx, 2) + Math.pow(ty, 2) > Math.pow(outer_radius - inner_radius, 2)) {
double angle = Math.atan(ty / tx);
//设置摇杆偏移点为当前触屏点和圆心连线与背景半径相交处
//Math.signum(tx):为确定偏移量的正负性
//outer_radius - inner_radius : 如果不减去inner_radius,则会出现摇杆圆心在背景圆的边缘,摇杆超出背景圆的现象,这样能保证摇杆圆的边缘和背景圆的边缘相切
//通过cos和sin函数计算x,y的偏移量
position.set(Math.signum(tx) * (outer_radius - inner_radius) * (float) Math.cos(angle), Math.signum(tx) * (outer_radius - inner_radius) * (float) Math.sin(angle));
} else
//没有超出就按触屏点的偏移量偏移摇杆
position.set(tx, ty);
break;

//抬起 = 1
case MotionEvent.ACTION_UP:
//抬起操作以后把touchdownflag设置为false
touchDownFlag = false;
//并把position的偏移量设为零,即摇杆回到圆心
position.set(0, 0);
break;
}
if (listener != null)
listener.onMove(position.x / (outer_radius - inner_radius), position.y / (outer_radius - inner_radius));
return true;
}

八、public void setOnJoystickMoveListener && public interface onJoystickMoveListener

1
2
3
4
5
6
7
8
9
10
   //设置触屏事件移动监听
public void setOnJoystickMoveListener(onJoystickMoveListener listener) {
this.listener = listener;
}

//VJ功能开放接口
//用于在activities中使用时可以把外界的数据通过这个接口用VJ的图像移动显示出来
public interface onJoystickMoveListener {
void onMove(float dx, float dy);
}

九、public void JoystickChangeByGravity(int which)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void JoystickChangeByGravity(int which) {
//正对方向为手机屏幕为横放,且右手抓功能键
//which为1,即向前,摇杆偏移量设置X增加1/2个背景半径,Y无偏移量
//其余类似
if (which == 1) position.set(outer_radius / 2, 0);
else if (which == 2) position.set(0, outer_radius / 2);
else if (which == 3) position.set(outer_radius / 2 * -1, 0);
else if (which == 4) position.set(0, outer_radius / 2 * -1);

//此处可以按个人需求修改
//在本例中为了在VelocityTable显示偏移量
if (listener != null)
listener.onMove(position.x / (outer_radius - inner_radius), position.y / (outer_radius - inner_radius));
}

//重置摇杆的位置,即把摇杆置于圆心处
public void resetJoystick() {
position.set(0, 0);

if (listener != null)
listener.onMove(position.x / (outer_radius - inner_radius), position.y / (outer_radius - inner_radius));
}